/**
 * 
 * @Project HRL Simulation
 * @Date 20.06.2011
 * @Author Aebi Pascal, Kocher Marc, Sriranjan George
 * @School BFH-TI Biel, Projekt 1
 *
 */

package utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import elements.Load;
import exceptions.ResourceException;


public class Agenda {

	private SkipList entries;
	private final int SL_END = Integer.MAX_VALUE;
	private final int SL_START = new Integer(-1);
	
	
	/**
	 * create a new agenda with an empty skip list
	 */
	public Agenda(){
		this.entries = new SkipList(this.SL_START, this.SL_END, 0);
	}
	
	
	/**
	 * add new AgendaEntry
	 * 
	 * @param ae AgendaEntry
	 * @return Locator of this Entry, null if new agenda entry doesn't fit in
	 * @throws ResourceException couldnt add entry
	 */
	public Locator addEntry(AgendaEntry ae) throws ResourceException{
		
		Iterator<?> prevIt = this.entries.locators();
		Iterator<?> nextIt = this.entries.locators();
		Locator prevLoc = null;
		Locator nextLoc = null;
		AgendaEntry prevAE = null;
		AgendaEntry nextAE = null;
		
		//put the second iterator ahead the first one 
		if(nextIt.hasNext()){
			nextIt.next();
		}
		//insert first entry
		else{
			return this.entries.insertItem(ae.getEndTime(), ae);
		}
		
		while(prevIt.hasNext()){
			prevLoc = (Locator)prevIt.next();
			prevAE = (AgendaEntry)prevLoc.element();
			
			//get entry for comparing, if no more return result
			if(nextIt.hasNext()){
				nextLoc = (Locator)nextIt.next();
				nextAE = (AgendaEntry)nextLoc.element();
			}else{
				nextAE = prevAE;
			}
			
			//insert between two entries or at the end
			if((ae.getEndTime() <= nextAE.getEndTime()) ||
			   (prevAE.getEndTime() <= ae.getStartTime() && ae.getEndTime() <= nextAE.getStartTime()) || 
			   (nextAE.getEndTime() <= ae.getStartTime())){
				return this.entries.insertItem(ae.getEndTime(), ae);
			}else{
				throw new ResourceException("Couldnt add entry, lastAE >= startTime -> return value = null\nlast entry endTime: " + prevAE.getEndTime()+ "\nadding entry: "+ae);
			}
		}
		throw new ResourceException("Unexpected exception in add entry");
	}
	
	
	/**
	 * remove entry from list
	 * @param ae entry to remove
	 */
	public void removeEntry(AgendaEntry ae){
		Iterator<?> it = this.entries.locators();
		Locator loc = null;
		AgendaEntry tempAE = null;
		
		while(it.hasNext()){
			loc = (Locator)it.next();
			tempAE = (AgendaEntry)loc.element();
			if(ae.equals(tempAE)){
				this.entries.removeItem(loc);
			}
		}
	}

	
	/**
	 * Gets the last inserted agenda entry
	 * 
	 * @return AgendaEntry - Returns the last inserted agenda entry
	 */
	public AgendaEntry getLastEntry(){
		Iterator<?> it = this.entries.locators();
		Locator loc = null;
		AgendaEntry ae = new AgendaEntry(0, null, 0, null, null);
		
		while(it.hasNext()){
			loc = (Locator)it.next();
			ae = (AgendaEntry)loc.element();
		}
		return ae;
	}
	
	
	public AgendaEntry getClosestEntryBefore(int time){
		AgendaEntry closestAE = null;
		
		//the skiplist doest return the right entry 
		Locator locAE = this.entries.findItem(time);
		if(locAE == null){
			//find closest before
			closestAE = (AgendaEntry) this.entries.findClosestBefore(time).element();
		}else{
			//key has been found
			closestAE = (AgendaEntry) locAE.element();
		}
		return closestAE;
	}
	
	
	public AgendaEntry getClosestEntryAfter(int time){
		AgendaEntry closestAE = null;
		closestAE = (AgendaEntry) this.entries.findClosestAfter(time).element();
		return closestAE;
	}
	
	
	// statistics
	// -------------------------------------------------------------------------------
  
	/**
	 * get average idle time (not used space in time table)
	 * @return double value - average idle time
	 */
	public double getAvgIdleTime(){
		Iterator<?> prevIt = this.entries.locators();
		Iterator<?> nextIt = this.entries.locators();
		Locator prevLoc = null;
		Locator nextLoc = null;
		AgendaEntry prevAE = null;
		AgendaEntry nextAE = null;
		int spaceCounter = 0;
		double value = 0;
		boolean isFirstTime = true;
		
		//put the second iterator ahead the first one 
		if(nextIt.hasNext()){
			nextIt.next();
		}else{
			//maybe zero or one entry so return idle time
			if(prevIt.hasNext()){
				prevLoc = (Locator)prevIt.next();
				prevAE = (AgendaEntry)prevLoc.element();
				return prevAE.getStartTime();
			}
			return 0;
		}
		
		while(prevIt.hasNext()){
			prevLoc = (Locator)prevIt.next();
			prevAE = (AgendaEntry)prevLoc.element();
			//if it is the first time  add initial idle time
			if(prevAE.getStartTime() >0 && isFirstTime){
				value = prevAE.getStartTime();
				spaceCounter++;
				isFirstTime = false;
			}
			//get entry for comparing, if no more return result
			if(nextIt.hasNext()){
				nextLoc = (Locator)nextIt.next();
				nextAE = (AgendaEntry)nextLoc.element();
			}else{
				if(spaceCounter == 0){
					return 0;
				}else{
					return value / spaceCounter;
				}
			}
			//in case there exists no space between entries (no idle time)
			if(prevAE.getEndTime() == nextAE.getStartTime()){
				continue;
			}
			//do work
			value = value + (nextAE.getStartTime() - prevAE.getEndTime());
			spaceCounter++;
		}
		return 0;
	}
	
	
	/**
	 * get average wait time (start position = end position)
	 * @return double - average wait time
	 */
	public double getAvgWaitTime(){
		Iterator<?> it = this.entries.locators();
		Locator loc = null;
		AgendaEntry currentAE = null;
		int waitCounter = 0;
		double value = 0;
		
		//there exists no entry 
		if(!it.hasNext()){
			return 0;
		}
		//do work
		while(it.hasNext()){
			loc = (Locator)it.next();
			currentAE = (AgendaEntry)loc.element();
			if(currentAE.getStartPosition() == currentAE.getEndPos()){
				value = value + (currentAE.getEndTime() - currentAE.getStartTime());
				waitCounter++;
			}
		}
		//return value if there exists else 0
		if(waitCounter>0){
			return value / waitCounter;
		}else{
			return 0;
		}
	}
	
	
	/**
	 * get average booking time (all entries belonging to a load)
	 * @return double - average booking time
	 */
	public double getAvgBookingTime(){
		Iterator<?> it = this.entries.locators();
		Locator loc = null;
		AgendaEntry currentAE = null;
		Load load = null;
		int loadCounter = 0;
		double value = 0;
		
		//there exists no entry 
		if(!it.hasNext()){
			return 0;
		}
		while(it.hasNext()){
			loc = (Locator)it.next();
			currentAE = (AgendaEntry)loc.element();
			load = currentAE.getLoad();
			int startTime = currentAE.getStartTime();
			int stopTime = currentAE.getEndTime();
			
			if(load == null){
				value = value + (stopTime - startTime);
				continue;
			}
			while(it.hasNext() && currentAE.getLoad() == load && currentAE.getLoad().getState() == load.getState()){
				loc = (Locator)it.next();
				currentAE = (AgendaEntry)loc.element();
				stopTime = currentAE.getEndTime();
			}
			value = value + (stopTime - startTime);
			loadCounter++;
			
		}
		//return value if there exists else 0
		if(loadCounter>0){
			return value / loadCounter;
		}else{
			return 0;
		}
	}
	
	
	/**
	 * get number of entries
	 * @return number of entries
	 */
	public int getNumberOfEntries(){
		Iterator<?> it = this.entries.locators();
		int counter = 0;
		while(it.hasNext()){
			it.next();
			counter++;
		}
		return counter;
	}


	// utilities
	// -------------------------------------------------------------------------------
  
	/**
	 * get all agenda entries
	 */
	public List<AgendaEntry> getAgendaEntries(){
		List<AgendaEntry> agendaEntries = new ArrayList<AgendaEntry>();
		
		Iterator<?> it = this.entries.locators();
		Locator loc = null;
		AgendaEntry tempAE = null;
		
		while(it.hasNext()){
			loc = (Locator)it.next();
			tempAE = (AgendaEntry)loc.element();
			agendaEntries.add(tempAE);
		}
		return agendaEntries;
	}
	
	
	/**
	 * sort all agena entries by start time
	 * @return sorted list
	 */
	public List<AgendaEntry> sortAgendaEntriesByStartTime(){
		List<AgendaEntry> agendaEntriesByEndTime = new ArrayList<AgendaEntry>();
		List<AgendaEntry> agendaEntriesByStartTime = new ArrayList<AgendaEntry>();
		
		agendaEntriesByEndTime.addAll(this.getAgendaEntries());
		Collections.sort(agendaEntriesByEndTime);
		
		for(int j = 0; j < agendaEntriesByEndTime.size(); j++){
			if (j<agendaEntriesByEndTime.size()-1){
				if(agendaEntriesByEndTime.get(j).getEndTime() == agendaEntriesByEndTime.get(j+1).getEndTime()){
					if(agendaEntriesByEndTime.get(j).getStartTime() < agendaEntriesByEndTime.get(j+1).getStartTime()){
						agendaEntriesByStartTime.add(agendaEntriesByEndTime.get(j));
					} else {
						agendaEntriesByStartTime.add(agendaEntriesByEndTime.get(j+1));
						agendaEntriesByStartTime.add(agendaEntriesByEndTime.get(j));
						j++;
					}
				} else {
					agendaEntriesByStartTime.add(agendaEntriesByEndTime.get(j));
				}
			} else {
				agendaEntriesByStartTime.add(agendaEntriesByEndTime.get(j));
			}
		}
		return agendaEntriesByStartTime;
	}


//	/**
//	 * find the AgendaEntry with a given key
//	 * 
//	 * @param key integer -> endTime
//	 * @return AgendaEntry object or null if not found
//	 */
//	public AgendaEntry findEntry(int key){
//		Locator loc = this.entries.findItem(key);
//		if(loc != null){
//			return (AgendaEntry)loc.element();
//		}else{
//			return null;
//		}
//	}
	
	
//	/**
//	 * add all entries
//	 * @param list of agenda entries
//	 */
//	public void addAllEntries(List<AgendaEntry> agendaEntries) {
//		for(AgendaEntry ae: agendaEntries){
//			this.addEntry(ae);
//		}
//	}
	
	
//	/**
//	 * print agenda with all entries
//	 */
//	public void printAllEntries(){
//		Iterator<?> it = this.entries.locators();
//		Locator loc = null;
//		
//		while(it.hasNext()){
//			loc = (Locator)it.next();
//			AgendaEntry ae = (AgendaEntry)loc.element();
//			System.out.println(ae);
//		}
//	}
	
	
//	/**
//	 * get next possible free time in agenda
//	 * 
//	 * @param time earliest time
//	 * @return possible time
//	 */
//	public int getNextFreeTime(int time){
//		AgendaEntry entry = (AgendaEntry)this.entries.findClosestAfter(time).element();
//		if(entry!=null)
//			return entry.getEndTime();
//		return time;
//	}
	
	
//	/**
//	 * Check if this block time is free
//	 * 
//	 * @param wishTime 
//	 * @param duration 
//	 * @return int - possible time
//	 */
//	public int getNextFreeTime(int wishTime, int duration){
//		
//		//if there exists no next entry return wish time
//		Locator locBefore = this.entries.findClosestBefore(wishTime);
//		if(locBefore.element() == null && !entries.hasNext(locBefore)){
//			return wishTime;
//		}
//		//get times of entry before
//		AgendaEntry entryBefore = (AgendaEntry) locBefore.element();
//		int entryBeforeEndTime = entryBefore.getEndTime();
//		
//		//search next entry if no entry exists returns wishTime
//		Locator locAfter = this.entries.findClosestAfter(wishTime);
//		if(locAfter.element() == null) return wishTime;
//		//get times of entry after
//		AgendaEntry entryAfter = (AgendaEntry) locAfter.element();
//		int entryAfterStartTime = entryAfter.getStartTime();
//		int entryAfterEndTime = entryAfter.getEndTime();
//		
//		if(entryBeforeEndTime < wishTime && entryAfterStartTime > wishTime+duration){
//			return wishTime;
//		}
//		//recursive call with new wish time
//		return getNextFreeTime(entryAfterEndTime, duration);
//	}
	
}
